package GP;

import java.util.ArrayList;
import java.util.Random;

import tree.Tree;
/**
 * implementation of Genetic programming algorithm
 *
 */
public class GPA {
	private final int populationSize = 700;
	private final int generationNumber = 500;
	private boolean isSUS = true;
	private Mutaion mutaion;
	private CrossOver crossOver;
	private Selection selection;
	private Random random;
	private ArrayList<Tree> population;
	
	
	public GPA() {
		random = new Random();
		mutaion = new Mutaion(random);
		crossOver = new CrossOver(random);
	}
	
	/*
	 * initialize the population
	 */
	public void initPopulation() {	
		population = new ArrayList<Tree>();
		for (int i=0;i<populationSize;i++){
				Tree t = new Tree(8);
				t.randomInit();
				//System.out.println(t);
				t.calcFitness();
				population.add(t);
						
		}
		//create selection instance with current population
		selection = new Selection(random,population);
	}
	//create next generation
	private void produceNextGeneration() {
		
		ArrayList<Tree> newPopulation = new ArrayList<Tree>();
		
		elitsmKillWorstChromosomes();
		elitsmAddBestChromosomes(newPopulation);
		selection.setPopulation(population);
		
		ArrayList<Tree> selectedPopulation = selectPopulation(); 
		while (newPopulation.size() < populationSize ) {
			Tree[] parents = select2Randomly(selectedPopulation);
			//first crossover
			Tree child = crossOver.crossOver(parents[0],parents[1]);
			mutaion.aggressiveMutation(child);//.softMutation(child);//
			newPopulation.add(child);
			//second cross over
			child = crossOver.crossOver(parents[1],parents[0]);
			mutaion.aggressiveMutation(child);//.softMutation(child);//
			newPopulation.add(child);
		}
		population=newPopulation;
	}
	
	private ArrayList<Tree> selectPopulation() {	
		ArrayList<Tree> selectedPopulation = null;
		if (isSUS){
			selectedPopulation = selection.selectionSUS();
		}else{
			selectedPopulation = selection.rouletteWheelSelection();
		}
		if (population.size()>selectedPopulation.size()){
			isSUS = false;
//			System.out.println("real population size :" + population.size());
//			System.out.println("selected population size : " + selectedPopulation.size());
//			System.out.println("activate roulette Wheel Selection..");
			selectedPopulation = selection.rouletteWheelSelection();
		}
		return selectedPopulation;
	}

	/**
	 * run GP algorithm
	 */
	public Tree runGPAlgorithm() {  
//		 System.out.println("before GP the best fitness is: " + 
//		    population.get(maxFitness(population,-1)).getFitness());
		  for (int i=0;i<generationNumber;i++){
		   produceNextGeneration();
//		   if (i % 50 == 0){
//		      System.out.println(//"genertaion "+ i + " the best fitness is: " + 
//		      population.get(maxFitness(population,-1)).getFitness());
//		   }  
		  }
		   return population.get(maxFitness(population,-1));
		}


	//select two items randomly and remove them from the array.
	private Tree[] select2Randomly(ArrayList<Tree> selectedPopulation) {
		int ind1 = random.nextInt(selectedPopulation.size());
		Tree parent1 = selectedPopulation.get(ind1);
		selectedPopulation.remove(ind1);
		int ind2 = random.nextInt(selectedPopulation.size());
		Tree parent2 = selectedPopulation.get(ind2);
		selectedPopulation.remove(ind2);
		return new Tree[]{parent1,parent2} ;
	}
	
	
	//----------------------- Elitism ---------------------------------//
	private void elitsmKillWorstChromosomes() {
		population.remove(minFitness(population));
		population.remove(minFitness(population));		
	}
	
	private void elitsmAddBestChromosomes(ArrayList<Tree> newPopulation) {
		int maxFitnessInd = maxFitness(population,-1);
		newPopulation.add(population.get(maxFitnessInd));

		maxFitnessInd = maxFitness(population,maxFitnessInd);
		newPopulation.add(population.get(maxFitnessInd));
	}
	
	///////------------Services function to Elitsm-----------------///////
	
	/*
	 * return the index of the minimum fitness in the array pop
	 */
	private int minFitness(ArrayList<Tree> pop){
		double min = pop.get(0).getFitness();
		int indexMin =0;
		for (int i=1; i<pop.size();i++){
			if (pop.get(i).getFitness()<min){
				min = pop.get(i).getFitness();
				indexMin = i;
			}
		}
		return indexMin;
	}
	
	/*
	 * return the index of the maximum fitness in the array pop
	 */
	private int maxFitness(ArrayList<Tree> pop, int lastIndMax){
		double max = Double.MIN_VALUE;
		int indexMax = 0;
		if (lastIndMax==0){
			indexMax = 1;
		}
		for (int i=0; i<pop.size();i++){
			if (i!=lastIndMax && pop.get(i).getFitness()>max){
				max = pop.get(i).getFitness();
				indexMax = i;
			}
		}
		return indexMax;
	}	

}
